---
order: 0
category: '@threlte/gltf'
title: Getting Started
---

A small command-line tool that turns GLTF assets into declarative and re-usable Threlte components.

## The GLTF workflow on the web is not ideal.

- GLTF is thrown wholesale into the scene which prevents re-use, in Three.js objects can only be mounted once
- Contents can only be found by traversal which is cumbersome and slow
- Changes to queried nodes are made by mutation, which alters the source data and prevents re-use
- Re-structuring content, making nodes conditional or adding/removing is cumbersome
- Model compression is complex and not easily achieved
- Models often have unnecessary nodes that cause extra work and matrix updates

## `@threlte/gltf` fixes that.

- It creates a **virtual graph** of all objects and materials. Now you can easily alter contents and re-use.
- The graph gets pruned (empty groups, unnecessary transforms, ...) for **better performance**.
- It will optionally compress your model with up to **70%-90% size reduction**.

## Usage

```bash
npx @threlte/gltf@latest /path/to/Model.glb [options]
```

<Tip type="warning">The CLI supports only `npx` at this moment.</Tip>

### Options

| Option                                                                | Description                                                          |
| --------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `--output, -o`                                                        | Output file name/path                                                |
| `--types, -t`                                                         | Add Typescript definitions                                           |
| `--keepnames, -k`                                                     | Keep original names                                                  |
| `--meta, -m`                                                          | Include metadata (as `userData`)                                     |
| `--shadows, -s`                                                       | Let meshes cast and receive shadows                                  |
| `--printwidth, -w`                                                    | Prettier `printWidth` (default: 120)                                 |
| `--precision, -p`                                                     | Number of fractional digits (default: 2)                             |
| `--draco, -d`                                                         | Draco binary path                                                    |
| `--preload -P`                                                        | Add preload method to module script                                  |
| `--suspense -u`                                                       | Make the component [suspense-ready](/docs/reference/extras/suspense) |
| `--isolated, -i`                                                      | Output as isolated module (No `$$restProps` usage)                   |
| `--root, -r`                                                          | Sets directory from which .gltf file is served                       |
| `--transform, -T`                                                     | Transform the asset for the web (draco, prune, resize)               |
| &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`--resolution, -R`                      | Transform resolution for texture resizing (default: 1024)            |
| &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`--simplify, -S`                        | Transform simplification (default: false, experimental)              |
| &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`--weld`  | Weld tolerance (default: 0.0001)                                     |
| &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`--ratio` | Simplifier ratio (default: 0.75)                                     |
| &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`--error` | Simplifier error threshold (default: 0.001)                          |
| `--debug, -D`                                                         | Debug output                                                         |

## Example

<Tip type="note">
  {' '}
  This example assumes you have your model set up and exported from an application like
  [Blender](https://www.blender.org/) as a GLTF file.
</Tip>

First you run your model through `@threlte/gltf`. `npx` allows you to use npm
packages without installing them.

```bash
npx @threlte/gltf@latest model.gltf --transform
```

This will create a `Model.svelte` file that plots out all of the assets
contents.

```svelte
<!--
Auto-generated by: https://github.com/pmndrs/gltfjsx
Command: npx gltfjsx@0.0.1 ./stacy.glb
-->
<script>
  import { Group } from 'three'
  import { T } from '@threlte/core'
  import { useGltf, useGltfAnimations } from '@threlte/extras'

  let {
    ref = $bindable(),
    actions = $bindable(),
    mixer = $bindable(),
    children,
    ...props
  } = $props()

  const gltf = useGltf('/stacy.glb')
  const animations = useGltfAnimations(gltf, ref)
  actions = animations.actions
  mixer = animations.mixer
</script>

{#if $gltf}
  <T.Group
    bind:ref
    {...props}
  >
    <T.Group name="Scene">
      <T.Group
        name="Stacy"
        rotation={[Math.PI / 2, 0, 0]}
        scale={0.01}
      >
        <T is={$gltf.nodes.mixamorigHips} />
        <T.SkinnedMesh
          name="stacy"
          geometry={$gltf.nodes.stacy.geometry}
          material={$gltf.nodes.stacy.material}
          skeleton={$gltf.nodes.stacy.skeleton}
          rotation={[-Math.PI / 2, 0, 0]}
          scale={100}
        />
      </T.Group>
    </T.Group>

    {@render children?.({ ref })}
  </T.Group>
{/if}
```

Add your model to your `/static` folder as you would normally do. With the
`--transform` flag it has created a compressed copy of it (in the above case
`model-transformed.glb`). Without the flag just copy the original model.

```text
static/
  model-transformed.glb
```

The component can now be dropped into your scene.

```svelte
<script>
  import { Canvas } from '@threlte/core'
  import Model from './Model.svelte'
</script>

<Canvas>
  <Model />
</Canvas>
```

You can re-use it, it will re-use geometries and materials out of the box:

```svelte
<Model position={[0, 0, 0]} />
<Model position={[10, 0, -10]} />
```

Or make the model dynamic. Change its colors for example:

```svelte
<T.Mesh
  geometry={$gltf.nodes.robot.geometry}
  material={$gltf.materials.metal}
  material.color="green"
/>
```

Or exchange materials:

```svelte
<T.Mesh geometry={$gltf.nodes.robot.geometry}>
  <T.MeshPhysicalMaterial color="hotpink" />
</T.Mesh>
```

Make contents conditional:

```svelte
{#if condition}
  <T.Mesh
    geometry={$gltf.nodes.robot.geometry}
    material={$gltf.materials.metal}
  />
{/if}
```

## DRACO Compression

You don't need to do anything if your models are draco compressed, since
`useGltf` defaults to a [draco CDN](https://www.gstatic.com/draco/v1/decoders/).
By adding the `--draco` flag you can refer to [local
binaries](https://github.com/mrdoob/three.js/tree/dev/examples/jsm/libs/draco/gltf)
which must reside in your /public folder.

## Auto-Transform

With the `--transform` flag it creates a binary-packed, draco-compressed,
texture-resized (1024x1024), webp compressed, deduped, instanced and pruned
\*.glb ready to be consumed on a web site. It uses
[glTF-Transform](https://github.com/donmccurdy/glTF-Transform). This can reduce
the size of an asset by 70%-90%.

It will not alter the original but create a copy and append
`[modelname]-transformed.glb`.

## Type-Safety

Add the `--types` flag and your component will be typesafe.

```svelte
<!--
Auto-generated by: https://github.com/pmndrs/gltfjsx
Command: npx gltfjsx@0.0.1 ./stacy.glb -t
-->
<script lang="ts">
  import type * as THREE from 'three'
  import { Group } from 'three'
  import { T, type Props } from '@threlte/core'
  import { useGltf, useGltfAnimations } from '@threlte/extras'
  import type { Snippet } from 'svelte'

  type Props = {
    ref?: Group
    actions?: ReturnType<typeof useGltfAnimations>['actions']
    mixer?: ReturnType<typeof useGltfAnimations>['mixer']
    children?: Snippet<[{ ref: Group }]>
  }

  let {
    ref = $bindable(),
    actions = $bindable(),
    mixer = $bindable(),
    children,
    ...props
  }: Props = $props()

  const group = new Group()

  type ActionName =
    | 'pockets'
    | 'rope'
    | 'swingdance'
    | 'jump'
    | 'react'
    | 'shrug'
    | 'wave'
    | 'golf'
    | 'idle'
  type GLTFResult = {
    nodes: {
      stacy: THREE.SkinnedMesh
      mixamorigHips: THREE.Bone
    }
    materials: {}
  }

  const { actions, mixer } = useGltfAnimations<ActionName>(gltf, ref)
  const gltf = useGltf<GLTFResult>('/stacy.glb')

  actions = animations.actions
  mixer = animations.mixer
</script>

{#if $gltf}
  <T
    bind:ref
    is={group}
    {...props}
  >
    <T.Group name="Scene">
      <T.Group
        name="Stacy"
        rotation={[Math.PI / 2, 0, 0]}
        scale={0.01}
      >
        <T is={$gltf.nodes.mixamorigHips} />
        <T.SkinnedMesh
          name="stacy"
          geometry={$gltf.nodes.stacy.geometry}
          material={$gltf.nodes.stacy.material}
          skeleton={$gltf.nodes.stacy.skeleton}
          rotation={[-Math.PI / 2, 0, 0]}
          scale={100}
        />
      </T.Group>
    </T.Group>

    {@render children?.({ ref })}
  </T>
{/if}
```

## Animations

If your GLTF contains animations it will add [@threlte/extras's
`useGltfAnimations`](/docs/reference/extras/use-gltf-animations) hook, which
extracts all clips and prepares them as actions:

```svelte
const gltf = useGltf('/stacy.glb') export const {(actions, mixer)} = useGltfAnimations(gltf, ref)
```

If you want to play an animation you can do so at any time:

```ts
const onEvent = () => {
  $actions.jump.play()
}
```

## Suspense

If you want to use the component [`<Suspense>`](/docs/reference/extras/suspense)
to suspend the rendering of loading components (and therefore models) and
optionally show a fallback in a parent component component, you can do so by
passing the flag `--suspense` to make the generated Threlte component
_suspense-ready_:

```svelte
<script>
  import Model from './Model.svelte'
  import Fallback from './Fallback.svelte'
  import { Suspense } from '@threlte/extras'
</script>

<Suspense>
  {#snippet fallback()}
    <Fallback />
  {/snippet}
  <Model />
</Suspense>
```

## Asset Pipeline

In larger projects with a lot of models and assets, it's recommended to set up
an asset pipeline with tools like
[`npm-watch`](https://www.npmjs.com/package/npm-watch) and Node.js scripts to
automatically transform models to Threlte components and copy them to the right
place as this makes iterating over models and assets much faster. Here's an
example script that you can use as a starting point:

```ts
import { execSync } from 'node:child_process'
import { copyFileSync, existsSync, mkdirSync, readdirSync, unlinkSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { exit } from 'node:process'

/**
 * This script is used to transform gltf and glb files into Threlte components.
 * It uses the `@threlte/gltf` package to do so.
 * It works in two steps:
 * 1. Transform the gltf/glb files located in the sourceDir directory
 * 2. Move the Threlte components to the targetDir directory
 */
const configuration = {
  sourceDir: resolve(join('static', 'models')),
  targetDir: resolve(join('src', 'lib', 'components', 'models')),
  overwrite: true,
  root: '/models/',
  types: true,
  keepnames: true,
  meta: false,
  shadows: false,
  printwidth: 120,
  precision: 2,
  draco: null,
  preload: false,
  suspense: false,
  isolated: true,
  transform: {
    enabled: false,
    resolution: 1024,
    simplify: {
      enabled: false,
      weld: 0.0001,
      ratio: 0.75,
      error: 0.001
    }
  }
} as const

// if the target directory doesn't exist, create it
mkdirSync(configuration.targetDir, { recursive: true })

// throw error if source directory doesn't exist
if (!existsSync(configuration.sourceDir)) {
  throw new Error(`Source directory ${configuration.sourceDir} doesn't exist.`)
}

// read the directory, filter for .glb and .gltf files and files *not* ending
// with -transformed.gltf or -transformed.glb as these should not be transformed
// again.
const gltfFiles = readdirSync(configuration.sourceDir).filter((file) => {
  return (
    (file.endsWith('.glb') || file.endsWith('.gltf')) &&
    !file.endsWith('-transformed.gltf') &&
    !file.endsWith('-transformed.glb')
  )
})

if (gltfFiles.length === 0) {
  console.log('No gltf or glb files found.')
  exit()
}

const filteredGltfFiles = gltfFiles.filter((file) => {
  if (!configuration.overwrite) {
    const componentFilename = file.split('.').slice(0, -1).join('.') + '.svelte'
    const componentPath = join(configuration.targetDir, componentFilename)
    if (existsSync(componentPath)) {
      console.error(`File ${componentPath} already exists, skipping.`)
      return false
    }
  }
  return true
})

if (filteredGltfFiles.length === 0) {
  console.log('No gltf or glb files to process.')
  exit()
}

filteredGltfFiles.forEach((file) => {
  // run the gltf transform command on every file
  const path = join(configuration.sourceDir, file)

  // parse the configuration
  const args: string[] = []
  if (configuration.root) args.push(`--root ${configuration.root}`)
  if (configuration.types) args.push('--types')
  if (configuration.keepnames) args.push('--keepnames')
  if (configuration.meta) args.push('--meta')
  if (configuration.shadows) args.push('--shadows')
  args.push(`--printwidth ${configuration.printwidth}`)
  args.push(`--precision ${configuration.precision}`)
  if (configuration.draco) args.push(`--draco ${configuration.draco}`)
  if (configuration.preload) args.push('--preload')
  if (configuration.suspense) args.push('--suspense')
  if (configuration.isolated) args.push('--isolated')
  if (configuration.transform.enabled) {
    args.push(`--transform`)
    args.push(`--resolution ${configuration.transform.resolution}`)
    if (configuration.transform.simplify.enabled) {
      args.push(`--simplify`)
      args.push(`--weld ${configuration.transform.simplify.weld}`)
      args.push(`--ratio ${configuration.transform.simplify.ratio}`)
      args.push(`--error ${configuration.transform.simplify.error}`)
    }
  }
  const formattedArgs = args.join(' ')

  // run the command
  const cmd = `npx @threlte/gltf@next ${path} ${formattedArgs}`
  try {
    execSync(cmd, {
      cwd: configuration.sourceDir
    })
  } catch (error) {
    console.error(`Error transforming model: ${error}`)
  }
})

// read dir again, but search for .svelte files only.
const svelteFiles = readdirSync(configuration.sourceDir).filter((file) => file.endsWith('.svelte'))

svelteFiles.forEach((file) => {
  // now move every file to /src/components/models
  const path = join(configuration.sourceDir, file)
  const newPath = join(configuration.targetDir, file)
  copyFile: try {
    // Sanity check, we checked earlier if the file exists. Still, the CLI takes
    // a while, so who knows what happens in the meantime.
    if (!configuration.overwrite) {
      // check if file already exists
      if (existsSync(newPath)) {
        console.error(`File ${newPath} already exists, skipping.`)
        break copyFile
      }
    }
    copyFileSync(path, newPath)
  } catch (error) {
    console.error(`Error copying file: ${error}`)
  }

  // remove the file from /static/models
  try {
    unlinkSync(path)
  } catch (error) {
    console.error(`Error removing file: ${error}`)
  }
})
```

Place this script in `scripts/transform-models.ts` and run it with `npx tsx scripts/model-pipeline.ts`.
